package net.zoneland.zrdp.framework.config;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import net.zoneland.zrdp.common.exception.ServiceException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;


/**
 * Mybatis支持*匹配扫描包
 *
 * @author zonevue
 */
@Configuration(proxyBeanMethods = false)
public class MyBatisConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyBatisConfig.class);

    @Value("${mybatis.typeAliasesPackage}")
    private String typeAliasesPackage;
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;
    @Value("${mybatis.configLocation}")
    private String configLocation;
    @Value("${mybatis.configuration.variables.tablePrefix}")
    private String tablePrefix;

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public static String setTypeAliasesPackage(final String typeAliasesPackage) {
        final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);

        final Set<String> allResult = Arrays.stream(StringUtils.split(typeAliasesPackage, ","))
            .map(String::trim)
            .flatMap(packageName -> scanPackageForResources(packageName, resolver, metadataReaderFactory))
            .collect(Collectors.toSet());

        if (allResult.isEmpty()) {
            throw new ServiceException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
        }

        return String.join(",", allResult);
    }

    private static Stream<String> scanPackageForResources(final String packageName, final ResourcePatternResolver resolver,
                                                       final MetadataReaderFactory metadataReaderFactory) {
        final String locationPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
            + ClassUtils.convertClassNameToResourcePath(packageName) + "/" + DEFAULT_RESOURCE_PATTERN;

        try {
            return Arrays.stream(resolver.getResources(locationPattern))
                .filter(Resource::isReadable)
                .map(resource -> getPackageNameFromResource(resource, metadataReaderFactory))
                .filter(Optional::isPresent)
                .map(Optional::get);
        } catch (final IOException e) {
            LOGGER.error("出现IO错误：{}", packageName, e);
            return Stream.empty();
        }
    }

    private static Optional<String> getPackageNameFromResource(final Resource resource, final MetadataReaderFactory metadataReaderFactory) {
        try {
            final MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            final String className = metadataReader.getClassMetadata().getClassName();
            return Optional.of(Class.forName(className).getPackage().getName());
        } catch (final ClassNotFoundException | IOException e) {
            LOGGER.error("出现错误：{}", resource.getDescription(), e);
            return Optional.empty();
        }
    }


    public Resource[] resolveMapperLocations(final String[] mapperLocations) {

        if (mapperLocations == null) {
            return new Resource[0];
        }

        final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        return Arrays.stream(mapperLocations)
            .flatMap(mapperLocation -> {
                try {
                    return Arrays.stream(resourceResolver.getResources(mapperLocation));
                } catch (IOException e) {
                    LOGGER.error("出现IO错误：{}", mapperLocation, e);
                    return Stream.empty();
                }
            })
            .toArray(Resource[]::new);
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(final DataSource dataSource) throws Exception {

        final String newTypeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(newTypeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        final Properties configurationProperties = new Properties();
        // 设置mybatis全局变量
        configurationProperties.put("tablePrefix", tablePrefix);
        sessionFactory.setConfigurationProperties(configurationProperties);
        return sessionFactory.getObject();
    }
}
